In [472]:
import math
import numpy as np
from scipy import signal
from PIL import Image, ImageDraw
from scipy.ndimage import gaussian_filter
import os

Part 1¶

Question 1-2

In [473]:
import ncc
In [474]:
def MakeGaussianPyramind(image, scale, minsize):
    '''
    purpose: creates a Gaussian pyramid for an image
    inputs: image - jpg, scale - factor by which to reduce image size, minsize - min dimension of reduced image
    outputs: list of [original_image, PIL images of reduced size]
    '''
    # generate appropriate sigma
    sigma=1/(2*scale)
    # open image
    im = Image.open(image)
    pyr = []
    pyr.append(np.float32(im))
    # set while loop with min dim as stop condition
    x, y = im.width, im.height
    min_dim = np.max([x, y])
    while int(min_dim*0.75) > minsize: # check condition
        if len(np.float32(im).shape) < 3: # if grayscale, do not split channels for separate filtering
            im2 = Image.fromarray(gaussian_filter(im, sigma))
            # resize image
            imnew = im2.resize((int(x*scale), int(y*scale)), Image.BICUBIC)
            # convert back to np array
            resized = np.float32(imnew)
            # set new ref image as recently downsampled one
            im = imnew
        else: # if RGB. apply filters separately
            r, g, b = im.split() # if multi-channel, split channels and filter before reassmbeling
            r, g, b = Image.fromarray(gaussian_filter(r, sigma)), Image.fromarray(gaussian_filter(g, sigma)), Image.fromarray(gaussian_filter(b, sigma))
            # clip values and merge channels
            im2 = Image.merge('RGB', (r, g, b))
            # resize image
            imnew = im2.resize((int(x*scale), int(y*scale)), Image.BICUBIC)
            # convert back to numpy arrays 
            r, g, b = imnew.split()
            tmp = Image.merge('RGB', (r, g, b))
            resized = np.float32(tmp)
            # set new ref image as recently downsampled one
            im = imnew
        pyr.append(resized)
        # update min_dim
        x, y = imnew.width, imnew.height
        min_dim = np.max([x, y])
    return pyr
        
In [475]:
print(os.getcwd())
filepath=r"hw2part2\faces\judybats.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
print('done')
c:\Users\Sadie\Documents\GitHub\CPSC_425\A2
done

Question 3

In [476]:
def ShowGaussianPyramid(pyr):
    '''
    purpose: display Gaussian pyramid
    inputs: list of np arrays
    outputs: None (display all images in pyramid side by side)
    '''
    heights = list(map(lambda x: x.shape[0], pyr)) # get dims of all imgs in list to know dims needed for template image
    widths = list(map(lambda x: x.shape[1], pyr))
    w_tot, h_tot = np.sum(widths), np.max(heights)
    image = Image.new(mode="L", size=(w_tot, h_tot), color="#ffff")
    offset_x, offset_y = 0, 0
    for im in pyr:
        im_pil = Image.fromarray(im)
        image.paste(im_pil,(offset_x, offset_y))
        offset_x += im_pil.width
        #offset_y = im_pil.height
    print(image.size)
    display(image)
In [477]:
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=100)
ShowGaussianPyramid(pyr)
(1089, 342)
No description has been provided for this image

Question 4

In [478]:
def FindTemplate(pyramid, template, threshold):
    '''
    purpose: find and mark regions in set of images where template matches above a certain threshold
    inputs: list of images (pyramid), template, threshold for matching
    outputs: annotated image
    '''
    # reduce template size
    temp_w = 15
    ar = template.height / template.width # keep same aspect ratio
    template = template.resize((temp_w,int(temp_w*ar)), Image.BICUBIC)
    # loop through images
    first_im = Image.fromarray(pyramid[0]).convert('RGB')
    colors=['red','orange','yellow','green','blue','purple','pink','black']
    for i, im in enumerate(pyramid):
        res = ncc.normxcorr2D(im, template)
        y, x = np.where(res > threshold)
        for j in range(len(x)):
            def rescale_x(x):
                ''' 
                purpose: rescale a x coordinate from a smaller image to correspond to the same location in a larger image
                input: x, the coordinate
                '''
                return (x/Image.fromarray(im).width)*first_im.width
            
            def rescale_y(y):
                ''' 
                purpose: rescale a y coordinate from a smaller image to correspond to the same location in a larger image
                input: y, the coordinate
                '''
                return (y/Image.fromarray(im).height)*first_im.height
            # create corners of box to be drawn
            temp_w, temp_h = template.width, template.height
            xc,yc = rescale_x(x[j]),rescale_y(y[j]) #,rescale_x(x2),rescale_y(y2)
            x1, x2 = xc - (temp_w/0.75**i)//2, xc + (temp_w/0.75**i)//2
            y1, y2 = yc - (temp_h/0.75**i)//2, yc + (temp_h/0.75**i)//2
            # rescale coordinates of high correlation for plotting on original image
            
            draw = ImageDraw.Draw(first_im)
            draw.line((x1,y1,x2,y1),fill=colors[i],width=2)
            draw.line((x1,y1,x1,y2),fill=colors[i],width=2)
            draw.line((x1,y2,x2,y2),fill=colors[i],width=2)
            draw.line((x2,y1,x2,y2),fill=colors[i],width=2)
            #del draw
    #display(first_im)
    #first_im.show()
    first_im.save("template_match.tif")
    return first_im
            
In [479]:
temp = Image.open(r"hw2part2\faces\template.jpg")
FindTemplate(pyr, temp, 0.72)
c:\Users\Sadie\Documents\GitHub\CPSC_425\A2\ncc.py:59: RuntimeWarning: divide by zero encountered in divide
  nxcorr = np.where(denom < tol, 0, numer/denom)
Out[479]:
No description has been provided for this image

Question 5

In [ ]:
# testing thresholds
thresh=0.8

filepath=r"hw2part2\faces\judybats.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\students.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\sports.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\family.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)

thresh=0.4

filepath=r"hw2part2\faces\tree.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\students.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\sports.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\family.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)

thresh=0.65

filepath=r"hw2part2\faces\judybats.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\students.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\tree.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
filepath=r"hw2part2\faces\fans.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
In [480]:
# best threshold
thresh=0.58
In [481]:
# judybats
filepath=r"hw2part2\faces\judybats.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
display(x)

# False positives: 1
# False negatives: 1
No description has been provided for this image
In [482]:
# students
filepath=r"hw2part2\faces\students.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
#ShowGaussianPyramid(pyr)
x = FindTemplate(pyr, temp, thresh)
display(x)

# False positives: 3
# False negatives: 4
No description has been provided for this image
In [483]:
filepath=r"hw2part2\faces\tree.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
display(x)
# False positives: 2
# False negatives: 0
No description has been provided for this image
In [484]:
filepath=r"hw2part2\faces\family.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
#ShowGaussianPyramid(pyr)
x = FindTemplate(pyr, temp, thresh)
display(x)
# False positives: 0
# False negatives: 1
No description has been provided for this image
In [485]:
filepath=r"hw2part2\faces\fans.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
display(x)
# False positives: 3
# False negatives: 3
No description has been provided for this image
In [486]:
filepath=r"hw2part2\faces\sports.jpg"
pyr = MakeGaussianPyramind(image=filepath, scale=0.75, minsize=50)
x = FindTemplate(pyr, temp, thresh)
display(x)
# False positives: 1
# False negatives: 1
No description has been provided for this image

A threshold of 0.58 achieves an approximately equal error rate across all tested images. While there is an approximately equal error rate across all images, this threshold shows the best performance for the 'students' image, where nearly all faces are correctly detected with only a small fraction of false negatives. Higher thresholds led to missed faces in several of the images, whereas lower thresholds led to a number of false positives, such as in the image of the tree where no true faces are present.

Question 6

Using a threshold of 0.58 on the NCC, the recall rates on each image are as follows:

In [487]:
import pandas as pd 

tps = [4,23,0,2,0,0]
pos = [5,27,0,3,3,1]
recall = []
for i in range(len(pos)):
    if pos[i] != 0:
        recall.append(tps[i]/pos[i])
    else:
        recall.append('undefined')

summary = {"Image": ['judybats', 'students', 'tree', 'family', 'fans', 'sports'], 
          "True Positives":tps, 
          "Positives":pos, 
          "Recall": recall
}
print(pd.DataFrame(summary))
      Image  True Positives  Positives     Recall
0  judybats               4          5        0.8
1  students              23         27   0.851852
2      tree               0          0  undefined
3    family               2          3   0.666667
4      fans               0          3        0.0
5    sports               0          1        0.0

The recall rate using NCC is rather low in particular for images that have very few actual positives for faces. For example, in the 'family' photo, while only 1 face was missed, the recall was only 66%. Thus recall seems like it is only a useful metric if there is a sizeable number of potential detections. Recall is also problematic as it does not report at all on false positives, which could be abundant amidst the correctly identified true positives, so this should be kept in mind as well.

Part 2: Image Blending

In [488]:
def MakeLaplacianPyramind(image, scale, minsize):
    '''
    purpose: creates a Laplacian pyramid for an image
    inputs: image - jpg, scale - factor by which to reduce image size, minsize - min dimension of reduced image
    outputs: list of [original_image, PIL images of reduced size]
    '''
    # initialize sigma
    sigma=1/(2*scale)
    # generate Gaussian pyramid
    Gauss_pyr = MakeGaussianPyramind(image, scale, minsize)
    Laplace_pyr = []
    for i, img in enumerate(Gauss_pyr):
        r, g, b = img[:,:,0], img[:,:,1], img[:,:,2]
        r2, g2, b2 = gaussian_filter(r, sigma), gaussian_filter(g, sigma), gaussian_filter(b, sigma)
        # clip to 0-255 and convert to image
        def clip_to(ch, min=0, max=255):
            return Image.fromarray(np.clip(ch, min, max).astype(np.uint8))
        r2_pil, g2_pil, b2_pil = clip_to(r2), clip_to(g2), clip_to(b2)
        # create smoothed image
        smoothed = Image.merge('RGB', (r2_pil, g2_pil, b2_pil))
        # if at end of pyramid, append tiny Gaussian
        if i == len(Gauss_pyr)-1:
            Laplace_pyr.append(np.float32(smoothed))
        # otherwise compute Laplacian as difference between original and blurred
        else:
            # subtract each channel
            r_dif, g_dif, b_dif = r-r2, g-g2, b-b2
            Laplace_img =  np.stack(np.array([r_dif, g_dif, b_dif]), axis=2)
            Laplace_pyr.append(np.float32(Laplace_img))
    return Laplace_pyr

Question 3

In [489]:
def ShowLaplacianPyramid(pyr):
    '''
    purpose: display Laplacian pyramid
    inputs: list of np arrays
    outputs: None (display all images in pyramid side by side)
    '''
    heights = list(map(lambda x: x.shape[0], pyr)) # get dims of all imgs in list to know dims needed for template image
    widths = list(map(lambda x: x.shape[1], pyr))
    w_tot, h_tot = np.sum(widths), np.max(heights)
    image = Image.new(mode="RGB", size=(w_tot, h_tot), color="#ffff")
    offset_x, offset_y = 0, 0
    for im in pyr:
        # adjust pixel values for viewing and discretize
        im += 128
        # normalize pixel range to 0-255 for improved contrast
        im = (im - np.min(im))/(np.max(im)-np.min(im))*255
        # clip
        im = np.clip(im, 0, 255).astype(np.uint8)
        # convert to RGB PIL Image
        r,g,b = Image.fromarray(im[:,:,0]), Image.fromarray(im[:,:,1]), Image.fromarray(im[:,:,2])
        im_pil = Image.merge('RGB', (r,g,b))
        image.paste(im_pil,(offset_x, offset_y))
        offset_x += im_pil.width
    display(image)
In [490]:
def ShowGaussianPyramidRGB(pyr):
    '''
    purpose: display Laplacian pyramid
    inputs: list of np arrays
    outputs: None (display all images in pyramid side by side)
    '''
    heights = list(map(lambda x: x.shape[0], pyr)) # get dims of all imgs in list to know dims needed for template image
    widths = list(map(lambda x: x.shape[1], pyr))
    w_tot, h_tot = np.sum(widths), np.max(heights)
    image = Image.new(mode="RGB", size=(w_tot, h_tot), color="#ffff")
    offset_x, offset_y = 0, 0
    for im in pyr:
        # adjust pixel values for viewing and discretize
        im = im.astype(np.uint8)
        # convert to RGB PIL Image
        r,g,b = Image.fromarray(im[:,:,0]), Image.fromarray(im[:,:,1]), Image.fromarray(im[:,:,2])
        im_pil = Image.merge('RGB', (r,g,b))
        image.paste(im_pil,(offset_x, offset_y))
        offset_x += im_pil.width
    display(image)
In [491]:
scale=0.75
minsize=50

# violet
filepath=r"hw2part2\violet.jpg"
violet_g = MakeGaussianPyramind(image=filepath, scale=scale, minsize=minsize)
ShowGaussianPyramidRGB(violet_g)
violet = MakeLaplacianPyramind(image=filepath, scale=scale, minsize=minsize)
ShowLaplacianPyramid(violet)
No description has been provided for this image
No description has been provided for this image
In [492]:
# orchid
filepath=r"hw2part2\orchid.jpg"
orchid_g = MakeGaussianPyramind(image=filepath, scale=scale, minsize=minsize)
ShowGaussianPyramidRGB(orchid_g)
orchid = MakeLaplacianPyramind(image=filepath, scale=scale, minsize=minsize)
ShowLaplacianPyramid(orchid)
No description has been provided for this image
No description has been provided for this image

Question 4

In [493]:
def ReconstructGaussianFromLaplacianPyramid(lPyramid):
    '''
    purpose: takes a Laplacian pyramid and reconstructs the Gaussian pyramid of that image from a Laplacian on
    input: lPyramid, a Laplacian pyramid
    output: gPyramid, a Gaussian pyramid
    '''
    gPyramid = []
    # add first entry to gPyramid since its the same as lPyramid
    gPyramid.append(lPyramid[-1])
    for i in range(1,len(lPyramid)):
        curr = gPyramid[-1]
        next = lPyramid[-i-1]
        # get size of next element in the pyr
        next_w, next_h = int(next.shape[1]), int(next.shape[0])
        # convert Gaussian to Image format for resizing
        r, g, b = Image.fromarray(curr[:,:,0].astype(np.uint8)), Image.fromarray(curr[:,:,1].astype(np.uint8)), Image.fromarray(curr[:,:,2].astype(np.uint8))
        curr_PIL = Image.merge('RGB', (r, g, b))
        # upsample/resize the image
        up_PIL = curr_PIL.resize((next_w, next_h), Image.BICUBIC)
        up = np.float32(up_PIL)
        # add details back using laplacian
        g_img = up + next
        # append reconstruction to gPyramid
        gPyramid.append(g_img)
    # reverse order of pyramid
    gPyramid.reverse()
    return gPyramid

Small Artifacts

In [494]:
# violet
filepath=r"hw2part2\violet.jpg"
violet_g1 = MakeGaussianPyramind(image=filepath, scale=scale, minsize=minsize)
violet_l = MakeLaplacianPyramind(image=filepath, scale=scale, minsize=minsize)
violet_g2 = ReconstructGaussianFromLaplacianPyramid(violet_l)
print('original')
ShowGaussianPyramidRGB(violet_g1)
print('reconstructed')
ShowGaussianPyramidRGB(violet_g2)

# orchid
filepath=r"hw2part2\orchid.jpg"
orchid_g1 = MakeGaussianPyramind(image=filepath, scale=scale, minsize=minsize)
orchid_l = MakeLaplacianPyramind(image=filepath, scale=scale, minsize=minsize)
orchid_g2 = ReconstructGaussianFromLaplacianPyramid(orchid_l)
print('original')
ShowGaussianPyramidRGB(orchid_g1)
print('reconstructed')
ShowGaussianPyramidRGB(orchid_g2)
original
No description has been provided for this image
reconstructed
No description has been provided for this image
original
No description has been provided for this image
reconstructed
No description has been provided for this image

Artifacts in the merged image due to aliasing can be mitigated by decreasing the sigma used for the blurring, however, this also reduces the integrity of the resulting reproduction (e.g., edges are less sharp, colours are less saturated).

In [495]:
# edit Gaussian pyramid function to reduce artifacts
# specifically, increase sigma from 1/2*scale to 1/2.3*scale
def MakeGaussianPyramind_noartifacts(image, scale, minsize):
    '''
    purpose: creates a Gaussian pyramid for an image
    inputs: image - jpg, scale - factor by which to reduce image size, minsize - min dimension of reduced image
    outputs: list of [original_image, PIL images of reduced size]
    '''
    # generate appropriate sigma
    sigma=1/(2.3*scale)
    # open image
    im = Image.open(image)
    pyr = []
    pyr.append(np.float32(im))
    # set while loop with min dim as stop condition
    x, y = im.width, im.height
    min_dim = np.max([x, y])
    while int(min_dim*0.75) > minsize: # check condition
        if len(np.float32(im).shape) < 3: # if grayscale, do not split channels for separate filtering
            im2 = Image.fromarray(gaussian_filter(im, sigma))
            # resize image
            imnew = im2.resize((int(x*scale), int(y*scale)), Image.BICUBIC)
            # convert back to np array
            resized = np.float32(imnew)
            # set new ref image as recently downsampled one
            im = imnew
        else: # if RGB. apply filters separately
            r, g, b = im.split() # if multi-channel, split channels and filter before reassmbeling
            r, g, b = Image.fromarray(gaussian_filter(r, sigma)), Image.fromarray(gaussian_filter(g, sigma)), Image.fromarray(gaussian_filter(b, sigma))
            # clip values and merge channels
            im2 = Image.merge('RGB', (r, g, b))
            # resize image
            imnew = im2.resize((int(x*scale), int(y*scale)), Image.BICUBIC)
            # convert back to numpy arrays 
            r, g, b = imnew.split()
            tmp = Image.merge('RGB', (r, g, b))
            resized = np.float32(tmp)
            # set new ref image as recently downsampled one
            im = imnew
        pyr.append(resized)
        # update min_dim
        x, y = imnew.width, imnew.height
        min_dim = np.max([x, y])
    return pyr

def MakeLaplacianPyramind_noartifacts(image, scale, minsize):
    '''
    purpose: creates a Laplacian pyramid for an image
    inputs: image - jpg, scale - factor by which to reduce image size, minsize - min dimension of reduced image
    outputs: list of [original_image, PIL images of reduced size]
    '''
    # initialize sigma
    sigma=1/(2.3*scale)
    # generate Gaussian pyramid
    Gauss_pyr = MakeGaussianPyramind(image, scale, minsize)
    Laplace_pyr = []
    for i, img in enumerate(Gauss_pyr):
        r, g, b = img[:,:,0], img[:,:,1], img[:,:,2]
        r2, g2, b2 = gaussian_filter(r, sigma), gaussian_filter(g, sigma), gaussian_filter(b, sigma)
        # clip to 0-255 and convert to image
        def clip_to(ch, min=0, max=255):
            return Image.fromarray(np.clip(ch, min, max).astype(np.uint8))
        r2_pil, g2_pil, b2_pil = clip_to(r2), clip_to(g2), clip_to(b2)
        # create smoothed image
        smoothed = Image.merge('RGB', (r2_pil, g2_pil, b2_pil))
        # if at end of pyramid, append tiny Gaussian
        if i == len(Gauss_pyr)-1:
            Laplace_pyr.append(np.float32(smoothed))
        # otherwise compute Laplacian as difference between original and blurred
        else:
            # subtract each channel
            r_dif, g_dif, b_dif = r-r2, g-g2, b-b2
            Laplace_img =  np.stack(np.array([r_dif, g_dif, b_dif]), axis=2)
            Laplace_pyr.append(np.float32(Laplace_img))
    return Laplace_pyr

# violet
filepath=r"hw2part2\violet.jpg"
violet_l = MakeLaplacianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)
violet_g2 = ReconstructGaussianFromLaplacianPyramid(violet_l)
print('original')
ShowGaussianPyramidRGB(violet_g1)
print('reconstructed-no artifacts')
ShowGaussianPyramidRGB(violet_g2)

# orchid
filepath=r"hw2part2\orchid.jpg"
orchid_l = MakeLaplacianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)
orchid_g2 = ReconstructGaussianFromLaplacianPyramid(orchid_l)
print('original')
ShowGaussianPyramidRGB(orchid_g1)
print('reconstructed-less artifacts')
ShowGaussianPyramidRGB(orchid_g2)
original
No description has been provided for this image
reconstructed-no artifacts
No description has been provided for this image
original
No description has been provided for this image
reconstructed-less artifacts
No description has been provided for this image

Question 5

In [496]:
filepath=r"hw2part2\orchid_mask.bmp"
gaussianM = MakeGaussianPyramind(image=filepath, scale=scale, minsize=minsize)
ShowGaussianPyramid(gaussianM)
(1840, 341)
No description has been provided for this image

Question 6

Despite the adjustment of the blurring prior to reconstruction, there is still some small artifacting in the resulting reconstructions. This seems to be a necessary trade off in order to preserve the edges and saturation of the original component images.

In [497]:
# Compose a single Laplacian pyramid from two, level by level
lapA = orchid_l
lapB = violet_l
compLaplacian = []
for i, lvl in enumerate(gaussianM):
    # reshape binary mask for compatibility with RGB
    mask = gaussianM[i].reshape(gaussianM[i].shape[0], gaussianM[i].shape[1], 1)
    entry = lapA[i] * mask/255 +  lapB[i] * (1 - mask/255)
    compLaplacian.append(entry)
# Reconstruct Gaussian from the composed Laplacian
recon=ReconstructGaussianFromLaplacianPyramid(compLaplacian)

# display the highest resolution image
curr = recon[0]
r, g, b = Image.fromarray(curr[:,:,0].astype(np.uint8)), Image.fromarray(curr[:,:,1].astype(np.uint8)), Image.fromarray(curr[:,:,2].astype(np.uint8))
res = Image.merge('RGB', (r, g, b))
display(res)
No description has been provided for this image
In [498]:
# once again, adjust sigma to reduce artifacts

def MakeGaussianPyramind_noartifacts(image, scale, minsize):
    '''
    purpose: creates a Gaussian pyramid for an image
    inputs: image - jpg, scale - factor by which to reduce image size, minsize - min dimension of reduced image
    outputs: list of [original_image, PIL images of reduced size]
    '''
    # generate appropriate sigma
    sigma=1/(2.6*scale)
    # open image
    im = Image.open(image)
    pyr = []
    pyr.append(np.float32(im))
    # set while loop with min dim as stop condition
    x, y = im.width, im.height
    min_dim = np.max([x, y])
    while int(min_dim*0.75) > minsize: # check condition
        if len(np.float32(im).shape) < 3: # if grayscale, do not split channels for separate filtering
            im2 = Image.fromarray(gaussian_filter(im, sigma))
            # resize image
            imnew = im2.resize((int(x*scale), int(y*scale)), Image.BICUBIC)
            # convert back to np array
            resized = np.float32(imnew)
            # set new ref image as recently downsampled one
            im = imnew
        else: # if RGB. apply filters separately
            r, g, b = im.split() # if multi-channel, split channels and filter before reassmbeling
            r, g, b = Image.fromarray(gaussian_filter(r, sigma)), Image.fromarray(gaussian_filter(g, sigma)), Image.fromarray(gaussian_filter(b, sigma))
            # clip values and merge channels
            im2 = Image.merge('RGB', (r, g, b))
            # resize image
            imnew = im2.resize((int(x*scale), int(y*scale)), Image.BICUBIC)
            # convert back to numpy arrays 
            r, g, b = imnew.split()
            tmp = Image.merge('RGB', (r, g, b))
            resized = np.float32(tmp)
            # set new ref image as recently downsampled one
            im = imnew
        pyr.append(resized)
        # update min_dim
        x, y = imnew.width, imnew.height
        min_dim = np.max([x, y])
    return pyr

def MakeLaplacianPyramind_noartifacts(image, scale, minsize):
    '''
    purpose: creates a Laplacian pyramid for an image
    inputs: image - jpg, scale - factor by which to reduce image size, minsize - min dimension of reduced image
    outputs: list of [original_image, PIL images of reduced size]
    '''
    # initialize sigma
    sigma=1/(2.6*scale)
    # generate Gaussian pyramid
    Gauss_pyr = MakeGaussianPyramind(image, scale, minsize)
    Laplace_pyr = []
    for i, img in enumerate(Gauss_pyr):
        r, g, b = img[:,:,0], img[:,:,1], img[:,:,2]
        r2, g2, b2 = gaussian_filter(r, sigma), gaussian_filter(g, sigma), gaussian_filter(b, sigma)
        # clip to 0-255 and convert to image
        def clip_to(ch, min=0, max=255):
            return Image.fromarray(np.clip(ch, min, max).astype(np.uint8))
        r2_pil, g2_pil, b2_pil = clip_to(r2), clip_to(g2), clip_to(b2)
        # create smoothed image
        smoothed = Image.merge('RGB', (r2_pil, g2_pil, b2_pil))
        # if at end of pyramid, append tiny Gaussian
        if i == len(Gauss_pyr)-1:
            Laplace_pyr.append(np.float32(smoothed))
        # otherwise compute Laplacian as difference between original and blurred
        else:
            # subtract each channel
            r_dif, g_dif, b_dif = r-r2, g-g2, b-b2
            Laplace_img =  np.stack(np.array([r_dif, g_dif, b_dif]), axis=2)
            Laplace_pyr.append(np.float32(Laplace_img))
    return Laplace_pyr

Cup

In [499]:
filepath=r"hw2part2\blue_cup.jpg"
lapB = MakeLaplacianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)
filepath=r"hw2part2\green_cup.jpg"
lapA = MakeLaplacianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)
# create mask
filepath=r"hw2part2\cup_mask.bmp"
gaussianM = MakeGaussianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)

# Compose a single Laplacian pyramid from two, level by level
compLaplacian = []
for i, lvl in enumerate(gaussianM):
    # reshape binary mask for compatibility with RGB
    mask = gaussianM[i].reshape(gaussianM[i].shape[0], gaussianM[i].shape[1], 1)
    entry = lapA[i] * mask/255 +  lapB[i] * (1 - mask/255)
    compLaplacian.append(entry)
# Reconstruct Gaussian from the composed Laplacian
recon=ReconstructGaussianFromLaplacianPyramid(compLaplacian)
#ShowGaussianPyramidRGB(recon)

# display the highest resolution image
curr = recon[0]
r, g, b = Image.fromarray(curr[:,:,0].astype(np.uint8)), Image.fromarray(curr[:,:,1].astype(np.uint8)), Image.fromarray(curr[:,:,2].astype(np.uint8))
res = Image.merge('RGB', (r, g, b))
display(res)
No description has been provided for this image

Fruit

In [500]:
filepath=r"hw2part2\tomato.jpg"
lapA = MakeLaplacianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)
filepath=r"hw2part2\apple.jpg"
lapB = MakeLaplacianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)
# create mask
filepath=r"hw2part2\tomato_mask.bmp"
gaussianM = MakeGaussianPyramind_noartifacts(image=filepath, scale=scale, minsize=minsize)

# Compose a single Laplacian pyramid from two, level by level
compLaplacian = []
for i, lvl in enumerate(gaussianM):
    # reshape binary mask for compatibility with RGB
    mask = gaussianM[i].reshape(gaussianM[i].shape[0], gaussianM[i].shape[1], 1)
    entry = lapA[i] * mask/255 +  lapB[i] * (1 - mask/255)
    compLaplacian.append(entry)
# Reconstruct Gaussian from the composed Laplacian
recon=ReconstructGaussianFromLaplacianPyramid(compLaplacian)
#ShowGaussianPyramidRGB(recon)

# display the highest resolution image
curr = recon[0]
r, g, b = Image.fromarray(curr[:,:,0].astype(np.uint8)), Image.fromarray(curr[:,:,1].astype(np.uint8)), Image.fromarray(curr[:,:,2].astype(np.uint8))
res = Image.merge('RGB', (r, g, b))
display(res)
No description has been provided for this image